#include <webx/metric.h>
#include <dbx/SQLiteConnect.h>

class MonitorModule : public webx::ProcessBase
{
protected:
	int process();
};

HTTP_WEBCGI(CGI_PRIVATE, "${filename}")
DEFINE_HTTP_CGI_EXPORT_FUNC(MonitorModule)

static HttpServer* app = HttpServer::Instance();

JsonEntity(Metric)
{
	rlong(type);
	rlong(value);
	rlong(times);
	rlong(ctime);
	rstring(name);
};

JsonEntity(RouteItem)
{
	rint(id);
	rint(port);
	rint(enabled);
	rstring(host);
	rstring(name);
};

class Monitor : public Object
{
	SpinMutex mtx;
	map<string, Metric> datamap;

	void update(const string& name, int type, int value)
	{
		SpinLocker lk(mtx);
		auto it = datamap.find(name);

		if (it == datamap.end())
		{
			Metric& item = datamap[name];

			item.type = type;
			item.name = name;
			item.value = value;
			item.ctime = time(NULL);
		}
		else
		{
			Metric& item = it->second;

			item.value = (type == webx::METRIC_SET ? value : item.value + value);
		}
	}

public:
	static Monitor* Instance()
	{
		static Monitor monitor;

		return &monitor;
	}
	void set(const string& name, int value)
	{
		update(name, webx::METRIC_SET, value);
	}
	void add(const string& name, int value = 1)
	{
		update(name, webx::METRIC_ADD, value);
	}
	void upload(function<void(const map<string, Metric>& datamap)> func)
	{
		map<string, Metric> uploadmap;

		mtx.lock();

		std::swap(uploadmap, datamap);

		mtx.unlock();

		func(uploadmap);
	}
};

class GraspTimer : public WorkItem
{
	JsonEntity(Result)
	{
		rlong(etime);
		rint(clientid);
		rint(clientport);
		rstring(clienthost);
		rstring(clientname);
		rarray(Metric, list);
	};

	JsonEntity(MetricEntity)
	{
		rlong(value);
		rlong(ctime);
		rlong(etime);
		rstring(host);
	};

	sp<RouteItem> route;

public:
	void run()
	{
		if (grasp())
		{
			LogTrace(eDBG, "grasp host[%s][%s][%d] monitor success", route->name.c_str(), route->host.c_str(), route->port);
		}
		else
		{
			LogTrace(eERR, "grasp host[%s][%s][%d] monitor failed", route->name.c_str(), route->host.c_str(), route->port);
		}
	}
	bool grasp()
	{
		Result result;
		sp<Socket> sock = newsp<Socket>();
		HttpRequest request("monitormodule");

		CHECK_FALSE_RETURN(sock->connect(route->host, route->port));

		SmartBuffer data = request.getResult(sock);

		CHECK_FALSE_RETURN(data.str() && result.fromString(data.str()));
		CHECK_FALSE_RETURN(result.etime > 0 && webx::IsFileName(result.clientname));

		MetricEntity entity;
		map<string, vector<sp<Metric>>> pathmap;

		for (const auto& item : result.list.getList())
		{
			string filepath = webx::GetMetricSQLitePath(result.clientname, item->name, DateTime(item->ctime));
	
			if (filepath.empty())
			{
				LogTrace(eERR, "invalid metric[%s][%s]", result.clientname.c_str(), item->name.c_str());

				continue;
			}
	
			pathmap[filepath].push_back(item);
		}

		entity.etime = result.etime;
		entity.host = result.clienthost;

		for (const auto& item : pathmap)
		{
			sp<DBConnect> dbconn = getDBConnect(item.first);

			if (!dbconn)
			{
				LogTrace(eERR, "connect monitor database[%s] failed", item.first.c_str());

				continue;
			}

			dbconn->begin();

			for (const auto& metric : item.second)
			{
				entity.value = metric->value;
				entity.ctime = metric->ctime;

				if (dbconn->insert(entity, "T_XG_METRIC") < 0)
				{
					LogTrace(eERR, "save metric[%s][%s] failed", result.clientname.c_str(), metric->name.c_str());

					break;
				}
			}

			dbconn->commit();
		}

		return true;
	}
	GraspTimer(sp<RouteItem> item) : route(item)
	{
	}
	sp<DBConnect> getDBConnect(const string& filepath)
	{
		bool flag = true;
		sp<SQLiteConnect> sqlite = newsp<SQLiteConnect>();

		if (!sqlite->connect(filepath))
		{
			path::mkdir(path::parent(filepath));

			if (!sqlite->connect(filepath)) return NULL;
		}

		static CacheMap<string, bool> initmap;
		static const char* createtable = MLSQL(
			CREATE TABLE T_XG_METRIC (
				HOST varchar(32) NOT NULL,
				VALUE bigint(20) NOT NULL,
				CTIME bigint(20) NOT NULL,
				ETIME bigint(20) NOT NULL
			);
			CREATE INDEX INDEX_METRIC_HOST ON T_XG_METRIC(HOST);
			CREATE INDEX INDEX_METRIC_CTIME ON T_XG_METRIC(CTIME);
		);

		if (initmap.get(filepath, flag)) return sqlite;

		if (sqlite->execute(createtable) < 0)
		{
			const char* sql = "SELECT * FROM T_XG_METRIC LIMIT 1";

			if (!sqlite->query(sql)) return NULL;
		}

		initmap.set(filepath, true);

		return sqlite;
	}
};

class MonitorTimer : public WorkItem
{
	Mutex mtx;
	time_t utime = 0;
	vector<sp<RouteItem>> hostlist;

public:
	void run()
	{
		if (app->getMonitorSwitch() > 0)
		{
			int delay = 0;
			Locker lk(mtx);
			time_t now = time(NULL);

			if (utime + 5 < now)
			{
				if (graspHost())
				{
					LogTrace(eINF, "grasp monitor host list success");

					utime = now;
				}
				else
				{
					LogTrace(eERR, "grasp monitor host list failed");
				}
			}

			for (const auto& item : hostlist)
			{
				stdx::delay(delay += 5, newsp<GraspTimer>(item));
			}
		}
	}
	bool graspHost()
	{
		HostItem route = webx::GetRegCenterHost();

		CHECK_FALSE_RETURN(route.canUse());

		sp<Socket> sock = newsp<Socket>();
		HttpRequest request("getrecordlist");

		CHECK_FALSE_RETURN(sock->connect(route.host, route.port));

		request.setParameter("tabid", "${route}");
		request.setParameter("pagesize", 1000);

		JsonEntity(Result)
		{
			rint(code);
			rarray(RouteItem, list);
		};

		Result result;
		vector<sp<RouteItem>> vec;
		SmartBuffer data = request.getResult(sock);

		CHECK_FALSE_RETURN(data.str() && result.fromString(data.str()));
		CHECK_FALSE_RETURN(result.code >= 0);

		for (const auto& item : result.list.getList())
		{
			if (item->enabled <= 0) continue;

			vec.push_back(item);
		}

		std::swap(hostlist, vec);

		return true;
	}
	static sp<MonitorTimer> Instance()
	{
		static sp<MonitorTimer> timer = newsp<MonitorTimer>();

		return timer;
	}
};

static void MetricAdd(const char* name, int value)
{
	if (name && value >= 0) Monitor::Instance()->add(name, value);
}

static void MetricSet(const char* name, int value)
{
	if (name && value >= 0) Monitor::Instance()->set(name, value);
}

EXTERN_DLL_FUNC int HttpPluginInit(HttpServer* app, const char* path)
{
	Process::SetObject("HTTP_METRIC_ADD_FUNC", (void*)MetricAdd);
	Process::SetObject("HTTP_METRIC_SET_FUNC", (void*)MetricSet);

	stdx::timer(1000, MonitorTimer::Instance());

	return XG_OK;
}

int MonitorModule::process()
{
	int res = 0;
	long etime = time(NULL);
	JsonElement arr = json.addArray("list");

	Monitor::Instance()->upload([&](const map<string, Metric>& datamap){
		for (const auto& data : datamap)
		{
			JsonElement item = arr[res++];

			data.second.toJsonElement(item);
		}
	});

	json["clienthost"] = app->getHost();
	json["clientport"] = app->getPort();
	json["clientname"] = app->getName();
	json["clientid"] = app->getId();
	json["etime"] = etime;
	json["code"] = res;
	out << json;

	return XG_OK;
}